﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WIA;
using System.Drawing;
using System.IO.IsolatedStorage;
using System.IO;
using System.Diagnostics;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;

namespace ImageAcquisitionTAL
{
    public class WIA20Technology : IAcquisitionTechnology
    {
        private Dictionary<string, DeviceInfo> installedDevices = new Dictionary<string, DeviceInfo>();

        public IEnumerable<ScannerInfo> GetInstalledScanners()
        {
            List<ScannerInfo> devs = new List<ScannerInfo>();
            DeviceManager wiaManager = new DeviceManagerClass();            
            foreach (DeviceInfo di in wiaManager.DeviceInfos)
            {
                installedDevices.Add(di.DeviceID, di);
                ScannerInfo s = new ScannerInfo();
                s.DeviceID = di.DeviceID;
                s.DisplayName = GetDeviceName(di);
                s.Technology = this;
                devs.Add(s);
            }
            return devs;
        }

        private string GetDeviceName(DeviceInfo di)
        {
            return GetProp(di, "Name") + " (" + GetProp(di, "Description") + ")";
        }

        public ScannerInfo GetScannerInfo(string deviceId)
        {
            DeviceInfo d = GetDevice(deviceId);
            ScannerInfo s = new ScannerInfo();
            if (d == null)
                return s;
            s.DeviceID = deviceId;
            s.DisplayName = GetDeviceName(d);
            s.Technology = this;
            s.SupportedResolutions = GetSupportedResolutions(d);
            s.SupportsFeeder = GetSupportsFeeder(d);
            GetSupportedColorModes(d, ref s.SupportsBW, ref s.SupportsGrayScale, ref s.SupportsColors);
            return s;
        }

        private void GetSupportedColorModes(DeviceInfo devInfo, ref bool BW, ref bool GrayScale, ref bool Colors)
        {
            // TODO: The hell I know? Let's do we support everything. I believe it's good for today's scanners
            BW = true;
            GrayScale = true;
            Colors = true;
            //Device dev = devInfo.Connect();
            //WIA.Property 
        }

        private bool GetSupportsFeeder(DeviceInfo devinfo)
        {
            Device dev = devinfo.Connect();
            WIA.Property capaProp = FindProp(dev.Properties, 3086);
            if (capaProp == null)
                return false;
            // TODO: understand constants. For a while it always true...
            return true;
        }

        private int[] GetSupportedResolutions(DeviceInfo devinfo)
        {
            // check only horizontal. Our pixels are square :)
            Device dev = devinfo.Connect();
            Item item = dev.Items[1];
            WIA.Property HorizResProp = FindProp(item.Properties, 6147);
            if (HorizResProp == null)
                return null;
            int[] result = new int[HorizResProp.SubTypeValues.Count];
            for (int i = 1; i <= result.Length; i++)
                result[i-1] = (int)GetSubTypeValuesItem(HorizResProp.SubTypeValues, i);
            return result;
        }

        private DeviceInfo GetDevice(string id)
        {
            if (string.IsNullOrEmpty(id))
                return null;
            if (installedDevices.ContainsKey(id))
                return installedDevices[id];
            else
                return null;
        }

        private string GetProp(DeviceInfo info, string name)
        {
            foreach (Property p in info.Properties)
                if (p.Name == name)
                    return p.get_Value().ToString();
            return null;
        }

        private int GetColorIntentByColorMode(ColorMode mode)
        {
            switch (mode)
            {
                case ColorMode.BW :
                    return (int)WiaImageIntent.TextIntent;
                case ColorMode.Color :
                    return (int)WiaImageIntent.ColorIntent;
                case ColorMode.GrayScale :
                    return (int)WiaImageIntent.GrayscaleIntent;
            }
            return (int)WiaImageIntent.UnspecifiedIntent;
        }

        public bool IsAvailable()
        {
            try
            {
                DeviceManager wiaManager = new DeviceManagerClass();
                wiaManager = null;
            }
            catch
            {
                return false;
            }
            return true;
        }

        public Image Acquire(ScannerInfo scanner)
        {
        Reprocess:
            DeviceInfo di = GetDevice(scanner.DeviceID);
#if DEBUG
            _DebugShowAllProps("DeviceInfo", di.Properties);
#endif
            Device d = di.Connect();
#if DEBUG
            _DebugShowAllProps("Device", d.Properties);
#endif
            try
            {                                
                Item item = d.Items[1];
#if DEBUG
                _DebugShowAllProps("Item[1]", item.Properties);
#endif
                if (scanner.CurrentSource == ScanSource.Feeder)
                    SetProp(d.Properties, WIA_PROPERTIES_WIA_DPS_DOCUMENT_HANDLING_SELECT, WIA_DPS_DOCUMENT_HANDLING_SELECT_FEEDER);
                else
                    SetProp(d.Properties, WIA_PROPERTIES_WIA_DPS_DOCUMENT_HANDLING_SELECT, WIA_DPS_DOCUMENT_HANDLING_SELECT_FLATBED);
                SetProp(item.Properties, 6147, scanner.CurrentResolution); // supported only square pixel
                SetProp(item.Properties, 6148, scanner.CurrentResolution); // supported only square pixel
                SetProp(item.Properties, 6146, GetColorIntentByColorMode(scanner.CurrentColorMode));
                int documentStatus = (int)GetProp(d.Properties, WIA_PROPERTIES_WIA_DPS_DOCUMENT_HANDLING_STATUS);
                if (documentStatus == 0 && scanner.CurrentSource == ScanSource.Feeder) // no more pages in feeder
                    return null;
                ICommonDialog dlg = new CommonDialogClass();
                ImageFile file = dlg.ShowTransfer(item, FormatID.wiaFormatPNG, false) as ImageFile;
                //ImageFile file = item.Transfer(FormatID.wiaFormatPNG) as ImageFile;
                if (file == null)
                    return null;
                byte[] bytes = file.FileData.get_BinaryData() as byte[];
                MemoryStream ms = new MemoryStream(bytes);
                Image result = Image.FromStream(ms);
                return result;
            }
            catch (COMException ex)
            {
                unchecked
                {
                    if (ex.ErrorCode == (int)0x8021000A)
                    {
                        WIAUtils.ResetWIA();
                        goto Reprocess;
                    } 
                    else if (ex.ErrorCode == (int)0x80210003)
                    {
                        return null;
                    }
                    else
                    {
                        throw new Exception("Error while acquiring image from scanner", ex);
                    }
                }
            }
            catch (Exception ex)
            {                
                throw new Exception("Error while acquiring image from scanner", ex);
            }
            finally
            {
                d = null;
            }
        }

        const int WIA_DPS_DOCUMENT_HANDLING_SELECT_FEEDER = 1;
        const int WIA_DPS_DOCUMENT_HANDLING_SELECT_FLATBED = 2;

        const int WIA_DPS_DOCUMENT_HANDLING_STATUS_FEED_READY = 1;

        const int WIA_PROPERTIES_WIA_RESERVED_FOR_NEW_PROPS = 1024;
        const int WIA_PROPERTIES_WIA_DIP_FIRST = 2;
        const int WIA_PROPERTIES_WIA_DPA_FIRST = WIA_PROPERTIES_WIA_DIP_FIRST + WIA_PROPERTIES_WIA_RESERVED_FOR_NEW_PROPS;
        const int WIA_PROPERTIES_WIA_DPC_FIRST = WIA_PROPERTIES_WIA_DPA_FIRST + WIA_PROPERTIES_WIA_RESERVED_FOR_NEW_PROPS;

        const int WIA_PROPERTIES_WIA_DPS_FIRST = WIA_PROPERTIES_WIA_DPC_FIRST + WIA_PROPERTIES_WIA_RESERVED_FOR_NEW_PROPS;
        const int WIA_PROPERTIES_WIA_DPS_DOCUMENT_HANDLING_STATUS = WIA_PROPERTIES_WIA_DPS_FIRST + 13;
        const int WIA_PROPERTIES_WIA_DPS_DOCUMENT_HANDLING_SELECT = WIA_PROPERTIES_WIA_DPS_FIRST + 14;

        const uint WIA_ERRORS_BASE_VAL_WIA_ERROR = 2149646336;
        const uint WIA_ERRORS_WIA_ERROR_PAPER_EMPTY = WIA_ERRORS_BASE_VAL_WIA_ERROR + 3;



        private object GetProp(WIA.Properties properties, int propId)
        {
            WIA.Property prop = FindProp(properties, propId);
            if (prop == null)
                return null;
            else
                return prop.get_Value();
        }

        private Property FindProp(WIA.Properties properties, int propId)
        {
            foreach (WIA.Property p in properties)
                if (p.PropertyID == propId)
                    return p;
            return null;
        } 

        private void SetProp(WIA.Properties properties, int propId, object propValue)
        {
            WIA.Property prop = FindProp(properties, propId);
            prop.set_Value(ref propValue);
        }

#if DEBUG
        private void _DebugShowAllProps(string msg, WIA.Properties props)
        {
            Debug.WriteLine(msg);
            Debug.WriteLine("---------------------------------------------------------------------------------");
            foreach (WIA.Property p in props)
            {
                string s = p.Name + "(" + p.PropertyID + ") = ";
                if (p.IsVector)
                    s = s + "[vector of data]";
                else
                {
                    s = s + p.get_Value();
                    try
                    {
                        if (p.SubType != WiaSubType.UnspecifiedSubType)
                        {
                            if (p.get_Value() != p.SubTypeDefault)
                            {
                                s = s + "(Default = " + p.SubTypeDefault + ")";
                            }
                        }
                    }
                    catch { };
                }

                if (p.IsReadOnly)
                {
                    s = s + " [READ ONLY]";
                }
                else
                {
                    if (p.SubType == WiaSubType.FlagSubType)
                    {
                        s = s + " [ valid flags include:";
                        for (int i = 1; i <= p.SubTypeValues.Count; i++)
                        {
                            s += GetSubTypeValuesItem(p.SubTypeValues, i);
                            if (i != p.SubTypeValues.Count)
                            {
                                s += ", ";
                            }
                        }
                        s += " ]";
                    }
                    else
                        if (p.SubType == WiaSubType.ListSubType)
                        {
                            s += " [ valid values include:";
                            try
                            {
                                for (int i = 1; i <= p.SubTypeValues.Count; i++)
                                {
                                    s += GetSubTypeValuesItem(p.SubTypeValues, i);
                                    if (i != p.SubTypeValues.Count)
                                    {
                                        s += ", ";
                                    }
                                }
                                s += " ]";
                            }
                            catch { }
                        }
                        else
                            if (p.SubType == WiaSubType.RangeSubType)
                            {
                                s = s + " [ valid values in the range from " +
                                    p.SubTypeMin + " to " + p.SubTypeMax +
                                    " in increments of " + p.SubTypeStep + " ]";
                            }
                }

                Debug.WriteLine(s);
            }
        }
#endif

        private object GetSubTypeValuesItem(Vector vector, int idx)
        {
            System.Collections.IEnumerator en = vector.GetEnumerator();
            en.Reset();
            for (int i = 1; i <= vector.Count; i++)
            {
                if (!en.MoveNext())
                    throw new Exception("Can't move next like count is telling");
                if (i == idx)
                    return en.Current;
            }
            return null;
        }
        
        public Image AcquireForPreview(ScannerInfo scanner)
        {
            ScannerInfo minimal = scanner.Clone() as ScannerInfo;
            if (scanner.SupportsBW)
                minimal.CurrentColorMode = ColorMode.BW;
            else if (scanner.SupportsGrayScale)
                minimal.CurrentColorMode = ColorMode.GrayScale;
            else
                minimal.CurrentColorMode = ColorMode.Color;
            minimal.CurrentResolution = scanner.SupportedResolutions[0];
            minimal.CurrentSource = ScanSource.Flatbed;
            return Acquire(minimal);
        }        

        public string Name
        {
            get
            {
                return "Microsoft WIA 2.0";
            }
        }

        bool sessionStarted;

        public object BeginAcquire(ScannerInfo scanner)
        {
            sessionStarted = true;
            return sessionStarted;
        }

        public void EndAcquire(object session)
        {
            sessionStarted = false;
        }

    }
}
